/*
 * Hai Phan
 * 
 * TCSS 342 - Winter 2010
 * Assignment 6B - Spreadsheet
 * March 4, 2010
 */

package gui;

import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import spreadsheet.Spreadsheet;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.print.PrinterException;

/**
 * SpreadSheetTable, keeps track of the 
 * Spreadsheet table of the GUI.
 * 
 * @author Hai Phan
 * @version March 4, 2010
 */
@SuppressWarnings("serial")
public class SpreadSheetTable extends JPanel implements TableModelListener  {
	
	/**
	 * Default width of the table GUI.
	 */
	private static final int WIDTH = 800;
	
	/**
	 * Default height of the table GUI.
	 */
	private static final int HEIGHT = 300;
	
	/**
	 * The textfield in the GUI.
	 */
	private JTextField my_display_formula;
	
	/**
	 * The spreadsheet table for the GUI.
	 */
	private JTable my_jtable;
	
	/**
	 * The spreadsheet internally in the program.
	 */
	private Spreadsheet my_spreadsheet;

	/**
	 * SpreadSheetTable constructor, initializes the SpreadSheetTable with
	 * default values.
	 * 
	 * @param the_formula the textfield that shows the formula of a cell.
	 */
	public SpreadSheetTable(final JTextField the_formula) {
        super(new GridLayout());
        my_spreadsheet = new Spreadsheet();
    	my_display_formula = the_formula;
    	setUpTable();
    }

	/**
	 * SpreadSheetTable constructor, initializes the SpreadSheetTable with
	 * new user values.
	 * 
	 * @param the_rows the number of rows in the spreadsheet.
	 * @param the_columns the number of columns in the spreadsheet.
	 * @param the_formula the textfield that shows the formula of a cell.
	 */
    public SpreadSheetTable(final int the_rows, final int the_columns, final JTextField the_formula) {
        super(new GridLayout());
        my_spreadsheet = new Spreadsheet(the_rows, the_columns);
    	my_display_formula = the_formula;
    	setUpTable();
    }
    
    /**
     * tableChanged is invoked when a cell gets changed by the user.
     * 
     * @param the_event the TableModelEvent event that was fired.
     */
	@Override
	public void tableChanged(TableModelEvent the_event) {
		int rows = the_event.getLastRow();
		int columns = the_event.getColumn();
		if (my_jtable.isEditing() && !my_jtable.getValueAt(rows, columns).equals("")) {
			String input = (String) my_jtable.getValueAt(rows, columns);
			settingUserInput(rows, columns, input);
			this.setJTableKeyListener();
		}
	}
	
	/**
	 * Allows you to print the data in the cells to a printer.
	 */
	public void printTable() {
		try {
			my_jtable.print();
		} catch (PrinterException e) {
			JOptionPane.showMessageDialog(null, e);
		}
	}
	
    /**
     * Sets up the SpreadSheetTable by setting up the size of the default panel,
     * constructs a new JTable, and adds it to a JScrollPane to allow scrolling,
     * and adding listeners to the JTable and formula textfield to listen for
     * key presses or mouse clicks.
     */
    private void setUpTable() {
    	this.setPreferredSize(new Dimension(WIDTH, HEIGHT)); 			//sets the size of the JTable
    	
        my_jtable = new JTable(my_spreadsheet.getCells(), generateColumnHeaders());
        JScrollPane scrollPane = new JScrollPane(my_jtable);			//adds the table to the scroll pane
        scrollPane.setRowHeaderView(new RowNumberTable(my_jtable));		//row headers
        my_jtable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);			//enables horizontal scrolling
        my_jtable.getTableHeader().setReorderingAllowed(false); 		//disable of rearranging the columns
        
        this.setJTableMouseListener();				//sets the JTable mouse listener
        this.setFormulaTextFieldKeyListener();		//sets the formula textfield key listener
        
        my_jtable.setColumnSelectionAllowed(true);
        my_jtable.setRowSelectionAllowed(true);
        my_jtable.getModel().addTableModelListener(this);
        this.add(scrollPane);
        
        my_display_formula.setFocusable(false);		//disallow a user to enter to the textfield when no cell is selected
    }
	
	/**
	 * Generate and returns a column header in a String array.
	 * For example: A, B,...,Z, AA, AB,...,BA...
	 * 
	 * @return a String array containing the column headers.
	 */
	private String[] generateColumnHeaders() {
		String[] column_num = new String[my_spreadsheet.getColumnSize()];
		char start_char = 'A';
        char[] alphabet = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 
        				   'H', 'I', 'J', 'K', 'L', 'M', 'N', 
        				   'O', 'P', 'Q', 'R', 'S', 'T', 'U', 
        				   'V', 'W', 'X', 'Y', 'Z'};
        int count = -1;
        for (int i = 0; i < my_spreadsheet.getColumnSize(); i++) {
        	if (count < 0) {
        		column_num[i] = "" + start_char++;
        	} else {
        		column_num[i] = "" + alphabet[count] + start_char++;
        	}
        	if (start_char > 'Z') {
        		start_char = 'A';
        		count++;
        	} 
        	if (count > 25) {
        		count = 0;
        	}
        }
        return column_num;
	}
	
	/**
	 * Updates the cells on the spreadsheet JTable GUI.
	 */
	private void updateCells() {
		int row = my_spreadsheet.getColumnSize();
		int column = my_spreadsheet.getColumnSize();
		
        for (int i = 0; i < row; i++) {
        	for (int j = 0; j < column; j++) {
        		my_jtable.setValueAt(my_spreadsheet.getCellValueString(i, j), i, j);
        	}
        }
	}
	
	/**
	 * Sets the JTable to have a MouseListener. When a mouse is pressed in a cell,
	 * the formula textfield is shown the appropriate formula for that cell.
	 */
	private void setJTableMouseListener() {
		my_jtable.addMouseListener(new MouseAdapter() {
			
			/**
			 * Invoked when a mouse is pressed in the JTable.
			 * Gets the current selected row/column and displays the formula
			 * in the textfield.
			 * 
			 * @param e the MouseEvent that was fired.
			 */
    		@Override
    		public void mousePressed(MouseEvent e) {
    			int select_row = my_jtable.getSelectedRow();
    			int select_col = my_jtable.getSelectedColumn();
    			if (select_row < 0 || select_col < 0) { 	//not selecting a row/column
    				my_display_formula.setText("");
    			} else {
    				my_display_formula.setText(my_spreadsheet.getCellFormula(select_row, select_col));
    			} 
    			my_display_formula.setFocusable(true);
    		}
        });
	}
	
	/**
	 * Sets the JTable to have a KeyListener. When a key is pressed
	 * (Enter, Left, Right, Up, or Down Arrow Keys), the cells on the GUI
	 * get updated properly.
	 */
	private void setJTableKeyListener() {
		my_jtable.addKeyListener(new KeyAdapter(){
			
			/**
			 * Invoked when a key is released in the JTable.  Gets the 
			 * current selected row/column and displays the formula
			 * in the textfield, finally updates the cells.
			 * 
			 * @param e the KeyEvent that was fired.
			 */
			@Override
			public void keyReleased(KeyEvent e) {
				int select_row = my_jtable.getSelectedRow();	
    			int select_col = my_jtable.getSelectedColumn();
    			
				switch (e.getKeyCode()) {
					case KeyEvent.VK_ENTER:
					case KeyEvent.VK_LEFT:
					case KeyEvent.VK_RIGHT:
					case KeyEvent.VK_UP:
					case KeyEvent.VK_DOWN:
						my_display_formula.setText(
								my_spreadsheet.getCellFormula(select_row, select_col));
						updateCells();
						break;
					default:
						break;
				}
			}
        });
	}
	
	/**
	 * Sets the formula textfield to have a KeyListener. When a key is pressed
	 * (Enter Key), the cells on the GUI get updated properly.
	 */
	private void setFormulaTextFieldKeyListener() {
		my_display_formula.addKeyListener(new KeyAdapter(){
			
			/**
			 * Invoked when a key is released (Enter) in the JTextField.
			 * 
			 * @param the_event the KeyEvent that was fired.
			 */
			@Override
	        public void keyPressed(KeyEvent the_event){
				switch (the_event.getKeyCode()) {
					case KeyEvent.VK_ENTER:
						int rows = my_jtable.getSelectedRow();
						int columns = my_jtable.getSelectedColumn();
						settingUserInput(rows, columns, my_display_formula.getText());
						updateCells();
						break;
					default:
						break;
				}
			}
		});
	}
	
	/**
	 * Sets the user input into the internal spreadsheet.
	 * 
	 * @param rows the current row the user is in.
	 * @param columns the current column the user is in.
	 * @param input the input the user tried to enter.
	 */
	private void settingUserInput(int rows, int columns, String input) {
		try {
			my_spreadsheet.setCellString(rows, columns, input);
		} catch (NumberFormatException e) {
			dealWithExceptions("ERROR: Bad Input!");
		} catch (ArrayIndexOutOfBoundsException e) {
			dealWithExceptions("ERROR: Bad Cell Reference!");
		} catch (NullPointerException e) {
			dealWithExceptions("ERROR: Bad Input!");
		} catch (Exception e) {
			if (e.getMessage().equals("Cycle Error")) {
				dealWithExceptions("ERROR: Cycle Error!");
			} else if (e.getMessage().equals("/ by zero")) {
				dealWithExceptions("ERROR: Divide by 0!");
			}
		}
	}
	
	/**
	 * Displays a message to the user indicating bad input,
	 * and sets the cell to 0.
	 * 
	 * @param the_msg the message to send to the user.
	 */
	private void dealWithExceptions(String the_msg) {
		fixUserError(my_jtable.getSelectedRow(), my_jtable.getSelectedColumn());
		JOptionPane.showMessageDialog(null, the_msg);
		my_display_formula.setText("0");
	}
	
	/**
	 * Resets the cell to 0, after the user enters bad data.
	 * 
	 * @param rows the current rows the user is in.
	 * @param columns the current column the user is in.
	 */
	private void fixUserError(int rows, int columns) {
		try {
			my_spreadsheet.setCellString(rows, columns, "0");
		} catch (Exception e) {
			if (e.getMessage().equals("Cycle Error")) {
				dealWithExceptions("ERROR: There is a cycle!");
			} 
		}
		my_jtable.setValueAt("0", rows, columns);
	}
	
}